package com.cyh;

import java.util.Hashtable;
import java.util.concurrent.locks.LockSupport;

/**
 * 二、共享模型：
 *   问题：对同一个变量静态变量，一个线程循环50000此加1，i++， 另外一个线程循环50000次减1,i--， 最终的结果不是0；
 *   多个线程有一个线程对资源写操作，就会有上面的问题。
 *   synchronized(对象)： 对象锁。 变量的读写操作都要加锁
 *   synchronized(对象1)和synchronized(对象2),两个对象不同，不能形成互斥。
 *   需要对所有的这个变量的写操作加锁。最好给读操作也加锁
 *   synchronized可以用在 代码块
 *   synchronized可以用在 成员方法 上， 因为成员方法有this，相当于synchronized(this){}
 *   synchronized可以用在 静态方法 上， 相当于synchronized(Test02.class){}
 *   静态方法上的锁跟成员变量上的锁，不是同一个对象，不会互斥。
 *   t1.start(),t2.start(); 线程的启动是没有顺序的，绝大部分t1先启动，也有可能t2先启动
 *   成员变量和静态变量线程不安全，局部变量线程安全， 但是局部变量return出去了对象用在新线程中或者局部变量中函数中又创建了新的线程，局部变量就不安全了
 *   局部变量线程安全的前提是这个变量没有用在新线程中。
 *
 *
 *   常见线程安全类：get() 和 put() 都是安全的。
 *     String
 *     Integer
 *     StringBuffer
 *     Random
 *     Vector
 *     Hashtable 类似实现map结构功能
 *     java.util.concurrent包下的类。
 *     这种线程安全指的是函数内的内容线程安全，如下：
 *     if(table.get("key") == null) { 第一个线程检查通过后刚好暂停了， 第二个线程也检查通过， 然后put了两次，而我们的要求是put一次。
 *         table.put("key", value);
 *     }
 *
 *
 *   wait/wait(1000)/notify/notifyAll 必须在获得了锁后才能调用这些方法
 *   调用wait方法，即可进入WAITING状态
 *   BLOCKED 和 WAITING 的线程都处于阻塞状态，不占用CPU时间片
 *   BLOCKED 线程会在释放锁时唤醒
 *   WAITING 线程会在线程调用notify/notifyAll时唤醒，但唤醒后并不意味着立即获得锁，仍需要进入EntryList重新竞争。
 *
 *
 *   sleep(long n) 和 wait(long n) 的区别
 *   1） sleep是Thread方法， 而wait是Object的方法
 *   2） sleep 不需要强制和synchronized配合使用， 但wait需要和synchronized一起使用
 *   3） sleep在睡眠的同时不会释放锁， 但wait在等待时候会释放对象锁， 所有wait不能用于对同一个变量的修改。
 *       sleep 休眠后，还有锁， 所以其他的synchronized(lock){}代码等待不会执行，只有等睡眠结束后，并且当前synchronized(){}代码执行完成后，才会执行另外一个。
 *       wait 休眠后，释放锁，所以另外的线程可以进入当前或者另外一个synchronized(lock){}中, 其实相当于锁暂时失效了。等执行synchronized或者notify时才重新上锁。
 *   4） 相同点： 它们的状态都是TIMED_WAITING
 *   5） sleep休眠后，其他线程中同一个锁的部分不能运行， wait休眠后，其他线程中同一个锁的部分能运行
 *
 *   有一个缺点，notify可能会唤醒其他的等待线程, 在送烟的等待线程上，增加一个送外卖等待线程，notify是随机唤醒其中一个。 所以这种情况要用notifyAll()
 *    但这样无法指定唤醒某个线程，解决方法： 将if判断改成while(), 说明唤醒后，会再次判断，不符合条件，会再次等待
 *
 *
 *  同步模式之保护性暂停：即GuardedSuspension, 用于一个线程等待另外一个线程的执行结果
 *     注：两个线程要关联同一个GuardedObject
 *        如果结果不断的从一个线程到另一个线程可以使用消息队列，而不是保护性暂停
 *        jdk中，join,Future的实现就是用此模式
 *        归类为同步模式
 *
 *
 *  join:一个线程等待另外一个线程结束： 原理就是：同步模式的保护性暂停来实现的
 *      join(): 一直等待
 *      join(long second): 等待超时
 *
 *  异步模式之生产者/消费者  文件：MessageQueue.java和Message.java
 *       注： 不需要生产结果和消费结果的线程一一对应
 *            消费队列可以平衡生产者额消费者的线程资源
 *            生产者只负责产生结果数据，不关心数据该如何处理，而消费者专心处理结果数据
 *            消息队列有容量限制，满时不再加入数据，空时不再消耗数据
 *            jdk中各种阻塞队列，采用的就是这种模式。
 *
 *
 *
 *  LockSupport.park(); //暂停当前线程
 *  LockSupport.unpark(暂停线程对象); //恢复某个线程的运行
 *  特点： 如果unpark先执行，对后执行的park也有效  --- 而wait不行
 *        不需要加锁
 *        可以指定唤醒某个线程
 *
 *
 *  多个不相干的功能加多把锁，可以用多个锁对象区分开来，相互不影响。
 *
 *
 *  //死锁，synchronized嵌套使用不同锁对象，会出现死锁
 *   检查死锁的方法一： 命令行jps 查找进程id 如1234， 在输入jstack 1234 找到logo 中的BLOCK或deadlock
 *           方法二：jconsole工具
 *
 *  活锁：表示一直执行不完的线程，比如两个线程，没有synchronized包裹， 但睡眠时间相同，while循环，符合条件退出，一个循环加1，一个循环减1，永远都不会结束
 *   解决方法，睡眠时间改成不一样
 *
 *  线程饥饿: 线程的优先级太低，始终得不到运行，很难出现
 * */
public class Test02 {
    static final Object lock1 = new Object(); //最好加上final, 表示引用不会再换
    static final Object lock2 = new Object();
    static final Object lock3 = new Object();

    public static void main(String[] args) throws InterruptedException {
        test1();
        test2();
        test3();
        test4();
        test5();
        test6();

    }

    public static void test1()  throws InterruptedException {
        //lock1.wait();//报错，因为只有获得锁时，才能调用这个方法
        synchronized (lock1) {
            lock1.wait();
        }

        new Thread(() -> {
            try {
                lock2.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t1").start();
        new Thread(() -> {
            try {
                lock2.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t2").start();
        Thread.sleep(1000);
        synchronized (lock2) {
            lock2.notify(); //随机一个线程唤醒lock2.wait();
            //lock2.notifyAll(); //划线所有lock2.wait();
        }
    }

    //有一个缺点，notify可能会唤醒其他的等待线程, 在送烟的等待线程上，增加一个送外卖等待线程，notify是随机唤醒其中一个。 所以这种情况要用notifyAll()
    //但这样无法指定唤醒某个线程，解决方法： 将if判断改成while(), 说明唤醒后，会再次判断，不符合条件，会再次等待
    public static boolean hasCigarette= false;
    public static void test2()  throws InterruptedException {
        new Thread(() -> {
            synchronized (lock3) {
                System.out.println("有烟没？" + hasCigarette);
                //if(!hasCigarette) {
                while(!hasCigarette) { //如果是外卖和烟同时等待时，都要用while代替if
                    System.out.println("没烟，先等一会");
                    try {
                        lock3.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                System.out.println("现在有烟码？" + hasCigarette);
                if(hasCigarette) {
                    System.out.println("可以开始抽烟了" );
                }
            }
        }, "小南").start();
        for(int i = 0; i < 5; i++) {
            new Thread(() -> {
                synchronized (lock3) {
                    System.out.println("边干活" );
                }
            }, "其他人").start();
        }
        Thread.sleep(1000);
        new Thread(() -> {
            synchronized (lock3) {
                hasCigarette = true;
                System.out.println("烟到了" );
                //lock3.notify();
                lock3.notifyAll(); //如果是外卖和烟同时等待时，都要用notifyAll代替notify
            }
        }, "送烟的").start();
    }

    //同步模式之保护性暂停
    public static  void test3() throws InterruptedException{
        GuardedObject guardedObject = new GuardedObject();
        new Thread(()->{
            guardedObject.get(); //这个会等待另外一个线程的结果
        }, "等待下载完成再执行").start();
        new Thread(() -> {
            Object o = new Object();
            try {
                Thread.sleep(1000);//模拟下载过程
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            guardedObject.complete(o); //通知下载完成了，另外一个线程可以做其他操作了。
        });
    }

    //异步模式之生产者/消费者
    public static void test4() throws InterruptedException {
        MessageQueue queue = new MessageQueue(2);
        for(int i = 0; i < 3; i++) {
            int id = i;
            new Thread(() -> {
                queue.put(new Message(id, "值" + id));
            }, "生产者" + i).start();
        }

        new Thread(() -> {
            while (true) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Message msg = queue.take();
            }
        }, "消费者").start();
    }

    //park  unpark
    public static void test5() throws InterruptedException {
        Thread t1 = new Thread(() -> {
            System.out.println("start ---");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("park ---");
            LockSupport.park();
            System.out.println("resume ---");
        }, "t1");

        t1.start();

        Thread.sleep(2000); //改成1000 并且 上面的1000改成2000， 结果一样，这时他的一个特点，unpark先执行，对后执行的park也有效
        System.out.println("unpark ---");
        LockSupport.unpark(t1);

    }

    //join
    public static void test6() throws InterruptedException{
        Thread t1 = new Thread(() -> {
            System.out.println("11111111");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("11111111===========");
        });
        Thread t2 = new Thread(() -> {
            System.out.println("222222222");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("222222222222===========");
        });
        t1.start();
        //等待t1结束，这时候t2线程并未启动
        t1.join();

        //t1结束后，启动t2线程
        t2.start();
        //等待t2结束
        t2.join();

        System.out.println("main end");
    }

    //死锁 A->B  B->A 相互等待导致死锁
    public static void test7() {
        Object A = new Object();
        Object B = new Object();
        new Thread(() -> {
            synchronized (A) {
                System.out.println("---1---a---");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (B) {
                    System.out.println("---1---b====");
                }
            }
        }).start();

        new Thread(() -> {
            synchronized (B) {
                System.out.println("---2---b---");
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (A) {
                    System.out.println("---2---a====");
                }
            }
        }).start();
    }

    //活锁 永远不会结束
    static int count = 10;
    public static void test8() {

        new Thread(() -> {
            while (count > 0) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                count--;
                System.out.println("---" + count);
            }
        }).start();

        new Thread(() -> {
            while (count < 20) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                count++;
                System.out.println("+++" + count);
            }
        }).start();
    }

}
